module Tablero where

import Char
import Maybe

data Color = Blanco | Negro deriving (Show, Eq, Ord)
type Posicion = (Char, Int) 
data Tablero = T (Posicion -> Maybe Color)

instance Show Tablero where
  show (T tablero) = 
      headerTablero1 ++ headerTablero2 ++
      concatMap showFil filas ++
      headerTablero2 ++ headerTablero1
    where
      showFil fil = show fil ++ " " ++
                    concatMap (showCol fil) columnas ++ "| " ++
                    show fil ++ "\n"
      showCol fil col = "|" ++ p (tablero (col,fil))
      p Nothing = " "
      p (Just Blanco) = "B"
      p (Just Negro) = "N"

------ Comienza solucion

{-
  Para hacer distitnos tests, decidimos poder utilizar tableros
  de distitnos anchos y altos, entoces definimos los valores
  ancho y alto, que ahora tiene asignado el numero 8 que cumple
  con el requisito del juego.
-}

-- Ej. 1

{-
  Representa un tablero vacio, 
  para cualquier posición del tablero devuelve Nothing.
-}
vacio :: Tablero
vacio = T (const Nothing)


{-
   Devuelve un tablero con dos fichas blancas y dos
   fichas negran en el centro del tablero.
-}
tableroInicial :: Tablero
tableroInicial =
  poner (chr(96 + ancho `div` 2 + 1), alto `div` 2 + 1) Negro (
    poner (chr(96 + ancho `div` 2), alto `div` 2) Negro (
      poner (chr(96 + ancho `div` 2 + 1), alto `div` 2) Blanco (
        poner (chr(96 + ancho `div` 2), alto `div` 2 + 1) Blanco (
          vacio
        )
      )
    )
  )


-- Ej. 2

{- Devuelve el color (o Nothing) de una posición en el tablero -}
contenido :: Posicion -> Tablero -> Maybe Color
contenido pos (T estado) = estado pos

{-
  Cambia el color de una posición en el tablero.
  Si la posición ya tenìa un color, lo reemplaza.
-}
poner :: Posicion -> Color -> Tablero -> Tablero
poner (col, fila) color t = T nuevoEstado
    where nuevoEstado (col2, fila2) | fila == fila2 && col == col2 = Just color
                                    | otherwise = contenido (col2, fila2) t

-- Ej. 3

{-
  Devuelve una posición desplazada la cantidad
  de filas indicada desde una posición original.
-}
desplazarFila :: Int -> Posicion -> Posicion
desplazarFila salto (col, fila) = (col, fila + salto)

{-
  Devuelve una posición desplazada la cantidad
  de columnas indicada desde una posición original.
-}
desplazarColumna :: Int -> Posicion -> Posicion
desplazarColumna salto (col, fila) = (chr(ord(col) + salto), fila)

{-
 Función auxiliar
 Devuelve una posición desplazada  
 x columnas e y filas desde la posición original
 haciendo uso de las funciones definidas previamente.
-}
desplazar :: Int -> Int -> Posicion -> Posicion
desplazar x y = (desplazarFila y).(desplazarColumna x)


-- Ej. 4
{-
  Devuelve una lista de posiciones válidas del tablero
  partiendo de una posición inicial y una función de desplazamiento.
  La función enRango está definida mas abajo y dice si una posición
  pertenece o no al tablero.
-}
generar :: Posicion -> (Posicion -> Posicion) -> [Posicion]
generar inicial despl = takeWhile enRango (iterate despl inicial)

-- Ej. 5

{-
  Devuelve la lista de posiciones que deberían cambiarse para finalizar la jugada, 
  asumiendo que la posición dada la ficha del color que está realizando el cambio
  (luego es distitno de Nothing).
  La idea de esta función es ir para las ocho direcciones posibles y ver si hay fichas que
  hay que invertir, y en caso de haberlas, devolver todas concatenadas en una lista. 
  Yendo en una posición, hay que invertir fichas si desde
  que uno se empieza a desplazar hasta la ultima ficha en esa dirección, son todas
  del color inverso al que se agregó, salvo por el último que es del mismo color.
  Explicación:
    direcciones es una lista de duplas de todas las ocho direcciones que hay que probar
    linea devuelve todas las casillas desde la posición actual, hasta la ultima posible
      dada una dirección, de en esta lista se excluye la posición actual
    color devuelve el color de la posición actual, que por precondición es distinto de Nothing
    aInvertirEnLinea se explica en la siguiente función
-}
posicionesAInvertir :: Posicion -> Tablero -> [Posicion]
posicionesAInvertir pos t =
        concat [aInvertirEnLinea color (linea dx dy) t | (dx, dy) <- direcciones]
  where
    color = fromJust (contenido pos t)
    direcciones = [(dx, dy) | dx <- [-1..1], dy <- [-1..1], dy /= 0 || dx /= 0]
    linea dx dy = tail (generar pos (desplazar dx dy))

{-
  Dado un color y una lista de posiciones devuelve, en caso que la ultima
  ficha sea de un color distinto a todo el resto, y ninguna sea Nothing
  la posición de toda las fichas a invertir, que serían todas salvo la ultima.
-}
aInvertirEnLinea :: Color -> [Posicion] -> Tablero -> [Posicion]
aInvertirEnLinea color xs t | snd linea == Just True = fst linea
                            | otherwise              = []
  where
    linea = auxInvertirEnLinea color xs t 


{-
  Recorre todas las posiciones y devuelve una dupla, donde si la segunda
  componente es Just True, la primer componente indica la lista de posiciones a invertir
  y si la segunda componente es Nothing o Just False, indica que no hay posiciones a invertir
  en esta linea.
-}
auxInvertirEnLinea :: Color -> [Posicion] -> Tablero -> ([Posicion], Maybe Bool)
auxInvertirEnLinea color ps t = foldl (auxReduce color t) ([], Nothing) ps

{-
  En caso que la segunda componente del tercer parametro sea Nothing (lo que representa
  que aún no se sabe si se pueden invertir fichas), si la posición actual 
  tiene el mismo color que el color que se pasa, devuelve true
  la lista de todas las posiciones que tenia (sin esta ultima) y que se pueden invertir.
  Si es Nothing, devuelve false, y no importa la primer componente, indicando que 
  no se pueden invertir colores. En otro caso mantiene el Nothing en la segunda componente
  diciendo que aún no se sabe, y la lista actual de posiciones a invertir en caso de devolver
  en otra llamada un True.
  Si la componete ya es True o False, simplemente se devuelve.
-}
auxReduce :: Color -> Tablero -> ([Posicion], Maybe Bool) -> Posicion -> ([Posicion], Maybe Bool)
auxReduce color t (acum, Nothing) curr | contenido curr t == Nothing    = (acum, Just False)
                                       | contenido curr t == Just color = (acum, Just True)
                                       | otherwise                      = (curr:acum, Nothing)
auxReduce color t acum curr = acum

-- Ej. 6
{-
  Invierte el color de todas las posiciones pasadas, se asume que
  en todas las posiciones hay una ficha.
-}
invertirTodas :: [Posicion] -> Tablero -> Tablero
invertirTodas = flip (foldr invertirUna)
        where invertirUna pos t = poner pos (colorContrario (fromJust (contenido pos t))) t

-- Auxiliares

{-
  Dice si una posición pertenece o no al tablero
-}
enRango :: Posicion -> Bool
enRango (col, fila) = fila >= 1 && fila <= alto && (ord(col) - 96) >= 1 && (ord(col) - 96) <= ancho

{- Dado un color da su color contrario -}
colorContrario :: Color -> Color
colorContrario Negro = Blanco
colorContrario Blanco = Negro

{- Define el ancho y alto del tablero a usar -}
ancho, alto :: Int
ancho = 8
alto  = 8

{- Lista de todas las columnas y filas del tablero -}
columnas = [chr(96 + x) | x <- [1..ancho]]
filas    = [alto, (alto-1) .. 1]

headerTablero1 = "  " ++ concat [' ':c:[] | c <- columnas] ++ "  \n"
headerTablero2 = "  " ++ concat ["--"     | c <- columnas] ++ "  \n"
